// A mini VGA library to handle undocumented X-modes.
// A large part of this code is taken from Robert C. Pendleton's article "Tweaked VGA Modes".
// Source: http://gameprogrammer.com/3-tweak.html

#include "gfx.h"
#include "mem.h"
#include "port.h"

#define VIDEO_SEG      0xA000

#define CRTC_ADDR      (0x3d4)      // base port of the CRT Controller (color)
#define CRTC_DATA      (0x3d5)      // data port of the CRT Controller (color)
#define SEQ_ADDR       (0x3c4)      // base port of the Sequencer
#define SEQ_DATA       (0x3c5)      // data port of the Sequencer
#define MOR_ADDR       (0x3c2)      // misc. output register
#define GCR_ADDR       (0x3ce)      // graphics controller address register
#define GCR_DATA       (0x3cf)      // graphics controller data register
#define IST1_ADDR      (0x3da)      // input status #1 register
#define ACR_ADDR       (0x3c0)      // attribute registers
#define PAL_READ_ADDR  (0x3c7)      // palette write address
#define PAL_WRITE_ADDR (0x3c8)      // palette write address
#define PAL_DATA       (0x3c9)      // palette data register


extern void gfx_getfont_addr(uint *seg, uint *offset);

static void set_mode(uchar mode) {
	interrupt(0x10, MAKE_16(mode,0),0,0,0);
}

// Private function used to initializes key components during the setup of a graphic mode.
// Called by the various gfx_initxxx functions.
static void init_globals(uint line_size, uint page_size, struct gfx_context *context) {
	// Enable all planes
	outp(SEQ_ADDR, 0x02);
	outp(SEQ_DATA, 0x0F);

	context->active_offset = 0;
	context->line_size = line_size;
	context->page_size = page_size;     // width * height / 4

	gfx_getfont_addr(&context->font_seg, &context->font_offs);

	gfx_setpage(0, context);
	gfx_clearpage(0, context);
	gfx_showpage(0, context);
}

// Initialize the VGA 320x240, 3 pages X-mode.
void gfx_init320x240(struct gfx_context *context) {
	int crtc;

	set_mode(0x13);

	outp(CRTC_ADDR, 0x11);
	crtc = inp(CRTC_DATA) & 0x7f;
	outp(CRTC_ADDR, 0x11);
	outp(CRTC_DATA, crtc);
	outp(MOR_ADDR, 0xe3);
	outp(CRTC_ADDR, 0x00); 
	outp(CRTC_DATA, 0x5f);
	outp(CRTC_ADDR, 0x01); 
	outp(CRTC_DATA, 0x4f);
	outp(CRTC_ADDR, 0x02); 
	outp(CRTC_DATA, 0x50);
	outp(CRTC_ADDR, 0x03); 
	outp(CRTC_DATA, 0x82);
	outp(CRTC_ADDR, 0x04); 
	outp(CRTC_DATA, 0x54);
	outp(CRTC_ADDR, 0x05); 
	outp(CRTC_DATA, 0x80);
	outp(CRTC_ADDR, 0x06); 
	outp(CRTC_DATA, 0x0d);
	outp(CRTC_ADDR, 0x07); 
	outp(CRTC_DATA, 0x3e);
	outp(CRTC_ADDR, 0x08); 
	outp(CRTC_DATA, 0x00);
	outp(CRTC_ADDR, 0x09); 
	outp(CRTC_DATA, 0x41);
	outp(CRTC_ADDR, 0x10); 
	outp(CRTC_DATA, 0xea);
	outp(CRTC_ADDR, 0x11); 
	outp(CRTC_DATA, 0xac);
	outp(CRTC_ADDR, 0x12); 
	outp(CRTC_DATA, 0xdf);
	outp(CRTC_ADDR, 0x13); 
	outp(CRTC_DATA, 0x28);
	outp(CRTC_ADDR, 0x14); 
	outp(CRTC_DATA, 0x00);
	outp(CRTC_ADDR, 0x15); 
	outp(CRTC_DATA, 0xe7);
	outp(CRTC_ADDR, 0x16); 
	outp(CRTC_DATA, 0x06);
	outp(CRTC_ADDR, 0x17); 
	outp(CRTC_DATA, 0xe3);
	outp(SEQ_ADDR, 0x01);
	outp(SEQ_DATA, 0x01);
	outp(SEQ_ADDR, 0x04);
	outp(SEQ_DATA, 0x06);
	outp(GCR_ADDR, 0x05);
	outp(GCR_DATA, 0x40);
	outp(GCR_ADDR, 0x06);
	outp(GCR_DATA, 0x05);
	inp(IST1_ADDR);
	outp(ACR_ADDR, 0x10 | 0x20);
	outp(ACR_ADDR, 0x41);
	inp(IST1_ADDR);
	outp(ACR_ADDR, 0x13 | 0x20);
	outp(ACR_ADDR, 0x00);
	outp(CRTC_ADDR, 0x11);
	crtc = inp(CRTC_DATA) | 0x80;
	outp(CRTC_ADDR, 0x11);
	outp(CRTC_DATA, crtc);

	init_globals(80, 19200, context);
}

// Initialize the VGA 400x300, 2 pages X-mode.
void gfx_init400x300(struct gfx_context *context) {
	int crtc;

	set_mode(0x13);

	outp(CRTC_ADDR, 0x11);
	crtc = inp(CRTC_DATA) & 0x7f;
	outp(CRTC_ADDR, 0x11);
	outp(CRTC_DATA, crtc);
	outp(MOR_ADDR, 0xe7);
	outp(CRTC_ADDR, 0x00); 
	outp(CRTC_DATA, 0x71);
	outp(CRTC_ADDR, 0x01); 
	outp(CRTC_DATA, 0x63);
	outp(CRTC_ADDR, 0x02); 
	outp(CRTC_DATA, 0x64);
	outp(CRTC_ADDR, 0x03); 
	outp(CRTC_DATA, 0x92);
	outp(CRTC_ADDR, 0x04); 
	outp(CRTC_DATA, 0x67);
	outp(CRTC_ADDR, 0x05); 
	outp(CRTC_DATA, 0x82);
	outp(CRTC_ADDR, 0x06); 
	outp(CRTC_DATA, 0x46);
	outp(CRTC_ADDR, 0x07); 
	outp(CRTC_DATA, 0x1f);
	outp(CRTC_ADDR, 0x08); 
	outp(CRTC_DATA, 0x00);
	outp(CRTC_ADDR, 0x09); 
	outp(CRTC_DATA, 0x40);
	outp(CRTC_ADDR, 0x10); 
	outp(CRTC_DATA, 0x31);
	outp(CRTC_ADDR, 0x11); 
	outp(CRTC_DATA, 0x80);
	outp(CRTC_ADDR, 0x12); 
	outp(CRTC_DATA, 0x2b);
	outp(CRTC_ADDR, 0x13); 
	outp(CRTC_DATA, 0x32);
	outp(CRTC_ADDR, 0x14); 
	outp(CRTC_DATA, 0x00);
	outp(CRTC_ADDR, 0x15); 
	outp(CRTC_DATA, 0x2f);
	outp(CRTC_ADDR, 0x16); 
	outp(CRTC_DATA, 0x44);
	outp(CRTC_ADDR, 0x17); 
	outp(CRTC_DATA, 0xe3);
	outp(SEQ_ADDR, 0x01);
	outp(SEQ_DATA, 0x01);
	outp(SEQ_ADDR, 0x02);
	outp(SEQ_DATA, 0x0f);
	outp(SEQ_ADDR, 0x04);
	outp(SEQ_DATA, 0x06);
	outp(GCR_ADDR, 0x05);
	outp(GCR_DATA, 0x40);
	outp(GCR_ADDR, 0x06);
	outp(GCR_DATA, 0x05);
	inp(IST1_ADDR);
	outp(ACR_ADDR, 0x10 | 0x20);
	outp(ACR_ADDR, 0x41);
	inp(IST1_ADDR);
	outp(ACR_ADDR, 0x13 | 0x20);
	outp(ACR_ADDR, 0x00);
	outp(CRTC_ADDR, 0x11);
	crtc = inp(CRTC_DATA) | 0x80;
	outp(CRTC_ADDR, 0x11);
	outp(CRTC_DATA, crtc);

	init_globals(100, 30000, context);
}

// Clear the current page to the given pixel value.
void gfx_clearpage(uchar color, struct gfx_context *context) {
	uint c = MAKE_16(color,color);
	// enable all planes
	outp(SEQ_ADDR, 0x02);
	outp(SEQ_DATA, 0x0F);
	memsetw(VIDEO_SEG, context->active_offset, c, context->page_size >> 1);
}

// Make all drawing go the the selected page.
void gfx_setpage(uint page, struct gfx_context *context) {
	context->active_offset = page * context->page_size;
}

// Make the selected page visible.
void gfx_showpage(uint page, struct gfx_context *context) {
	uint offset = page * context->page_size;
	while(inp(IST1_ADDR) & 0x01);       // wait for display disable
	outp(CRTC_ADDR, 0x0c);
	outp(CRTC_DATA, (offset & 0xff00) >> 8);
	outp(CRTC_ADDR, 0x0d);
	outp(CRTC_DATA, offset &0x00ff);
	// wait for vertical retrace
	while(!(inp(IST1_ADDR) & 0x08));
}

// Draw 4 consecutive pixels of the specified color.
void gfx_setpixel4(uint x, uint y, uchar color, struct gfx_context *context) {
	// enable all planes
	outp(SEQ_ADDR, 0x02);
	outp(SEQ_DATA, 0x0F);
	memputb(VIDEO_SEG, context->active_offset + (context->line_size * y) + (x >> 2), color);
}

// Draw a pixel of the specified color.
void gfx_setpixel(uint x, uint y, uchar color, struct gfx_context *context) {
	// set the mask so that only one pixel gets written
	outp(SEQ_ADDR, 0x02);
	outp(SEQ_DATA, 1 << (x & 0x3));
	memputb(VIDEO_SEG, context->active_offset + (context->line_size * y) + (x >> 2), color);
}

// Read the color of a pixel.
uchar gfx_getpixel(uint x, uint y, struct gfx_context *context) {
	// set the mask so that only one pixel gets read
	outp(GCR_ADDR, 0x04);
	outp(GCR_DATA, x & 0x3);
	return memgetb(VIDEO_SEG, context->active_offset + (context->line_size * y) + (x >> 2));
}

// Set the RGB values the palette at the specified index.
void gfx_setpalette(uchar index, uchar r, uchar g, uchar b) {
	outp(PAL_WRITE_ADDR, index);
	outp(PAL_DATA, r);
	outp(PAL_DATA, g);
	outp(PAL_DATA, b);
}

// Read the RGB values of the palette at the specified index.
void gfx_getpalette(uchar index, uchar *r, uchar *g, uchar *b) {
	outp(PAL_READ_ADDR, index);
	*r = inp(PAL_DATA);
	*g = inp(PAL_DATA);
	*b = inp(PAL_DATA);
}

// Render a character using the 8x8 BIOS font.
static void render_char(uint x, uint y, uchar color, char c, struct gfx_context *context) {
	uint i, j;
	uchar mask;
	uint font_offs = context->font_offs + ((uint)c << 3);

	for (i = 0; i < 8; i++)	{
		mask = memgetb(context->font_seg, font_offs++);
		for (j = 0; j < 8; j++) {
			if (mask & 0x80) {
				gfx_setpixel(x + j, y + i, color + i, context);
			}
			mask <<= 1;
		}
	}
}

// Display a string at the specified position starting with the specified color (uses the next 7 colors).
void gfx_print_string(uint x, uint y, uint color, char *str, struct gfx_context *context) {
	while (*str) {
                render_char(x, y, color, *str, context);
		str++;
		x += 8;
        }
}

// Close the graphic mode and go back to text mode (80x25 in 16 colors).
void gfx_close() {
	set_mode(3);
}
